/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.uml_generator.visitor.impl;

import java.util.HashSet;
import java.util.Set;

import com.genia.toolbox.basics.bean.Transformer;
import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.basics.manager.CollectionManager;
import com.genia.toolbox.uml_generator.manager.AssociationManager;
import com.genia.toolbox.uml_generator.model.AbstractClassModel;
import com.genia.toolbox.uml_generator.model.AbstractDelegatableObject;
import com.genia.toolbox.uml_generator.model.AssociationModel;
import com.genia.toolbox.uml_generator.model.AttributeModel;
import com.genia.toolbox.uml_generator.model.EnumerationModel;
import com.genia.toolbox.uml_generator.model.OperationModel;
import com.genia.toolbox.uml_generator.model.impl.ClassModel;
import com.genia.toolbox.uml_generator.model.impl.InterfaceModel;

/**
 * Visitor that enhance a set of object.
 */
public class EnhancerVisitor
    extends SimpleModelVisitor
{
  /**
   * objects already enhanced.
   */
  private Set<AbstractDelegatableObject> alreadyEnhancedClassModels = new HashSet<AbstractDelegatableObject>();

  /**
   * the {@link CollectionManager} to use.
   */
  private CollectionManager collectionManager;

  /**
   * the {@link AssociationManager} to use.
   */
  private AssociationManager associationManager;

  /**
   * a {@link Transformer} to tranform a {@link java.util.Collection} of
   * associations to their corresponding attributes.
   */
  private Transformer<AssociationModel, AttributeModel> attributeForAssociationTranformer = new Transformer<AssociationModel, AttributeModel>()
  {

    public AttributeModel transform(AssociationModel object)
        throws TechnicalIOException
    {
      return getAssociationManager().getInternalAttributeForAssociation(object);
    }

  };



  /**
   * the visitor method for an object of type {@link ClassModel}.
   * 
   * @param classModel
   *          the object being visited
   * @throws BundledException
   *           when an error occurred
   * @see com.genia.toolbox.uml_generator.visitor.impl.SimpleModelVisitor#visitClassModel(com.genia.toolbox.uml_generator.model.impl.ClassModel)
   */
  @Override
  public void visitClassModel(ClassModel classModel)
      throws BundledException
  {
    visitAbstractClassModel(classModel);
  }



  /**
   * the visitor method for an object of type {@link AbstractClassModel}.
   * 
   * @param classModel
   *          the object being visited
   * @throws BundledException
   *           when an error occurred
   * @see com.genia.toolbox.uml_generator.visitor.impl.SimpleModelVisitor#visitClassModel(com.genia.toolbox.uml_generator.model.impl.ClassModel)
   */
  private void visitAbstractClassModel(AbstractClassModel classModel)
      throws BundledException
  {
    if (classModel == null || alreadyEnhancedClassModels.contains(classModel)) {
      return;
    }
    alreadyEnhancedClassModels.add(classModel);
    Set<AbstractClassModel> interfacesToAdd = new HashSet<AbstractClassModel>();
    if (classModel.getParent() != null) {
      classModel.getParent().visit(this);
      interfacesToAdd.addAll(addClassModelProperties(classModel.getParent(), classModel));
    }
    for (AbstractClassModel interfaceModel : classModel.getImplementsModel()) {
      interfaceModel.visit(this);
      interfacesToAdd.addAll(addClassModelProperties(interfaceModel, classModel));
    }
    classModel.getImplementsModel().addAll(interfacesToAdd);
    classModel.getAttributes().addAll(getCollectionManager().transformSet(classModel.getAssociations(), attributeForAssociationTranformer));
  }



  /**
   * the visitor method for an object of type {@link EnumerationModel}.
   * 
   * @param enumerationModel
   *          the object being visited
   * @throws BundledException
   *           when an error occurred
   * @see com.genia.toolbox.uml_generator.visitor.impl.SimpleModelVisitor#visitEnumerationModel(com.genia.toolbox.uml_generator.model.EnumerationModel)
   */
  @Override
  public void visitEnumerationModel(EnumerationModel enumerationModel)
      throws BundledException
  {
    if (enumerationModel == null || alreadyEnhancedClassModels.contains(enumerationModel)) {
      return;
    }
    Set<AbstractClassModel> interfacesToAdd = new HashSet<AbstractClassModel>();
    for (AbstractClassModel interfaceModel : enumerationModel.getImplementsModel()) {
      interfaceModel.visit(this);
      interfacesToAdd.addAll(addDelegatableProperties(interfaceModel, enumerationModel));
    }
    enumerationModel.getImplementsModel().addAll(interfacesToAdd);
  }



  /**
   * the visitor method for an object of type {@link InterfaceModel}.
   * 
   * @param interfaceModel
   *          the object being visited
   * @throws BundledException
   *           when an error occurred
   * @see com.genia.toolbox.uml_generator.visitor.impl.SimpleModelVisitor#visitInterfaceModel(com.genia.toolbox.uml_generator.model.impl.InterfaceModel)
   */
  @Override
  public void visitInterfaceModel(InterfaceModel interfaceModel)
      throws BundledException
  {
    visitAbstractClassModel(interfaceModel);
  }



  /**
   * add the needed properties of the father to the son.
   * 
   * @param father
   *          the father {@link AbstractClassModel}
   * @param son
   *          the son {@link AbstractClassModel}
   * @return the {@link Set} of {@link AbstractClassModel} that the father must
   *         implement
   */

  private Set<AbstractClassModel> addClassModelProperties(AbstractClassModel father, AbstractClassModel son)
  {
    if (father == null) {
      return new HashSet<AbstractClassModel>();
    }
    for (AssociationModel associationModel : father.getAssociations()) {
      son.addAssociation(associationModel);
    }
    for (AttributeModel attributeModel : father.getAttributes()) {
      son.addAttribute(attributeModel);
    }
    return addDelegatableProperties(father, son);
  }



  /**
   * add the needed properties of the father to the son.
   * 
   * @param father
   *          the father {@link AbstractClassModel}
   * @param son
   *          the son {@link AbstractClassModel}
   * @return the {@link Set} of {@link AbstractClassModel} that the father must
   *         implement
   */
  private Set<AbstractClassModel> addDelegatableProperties(AbstractDelegatableObject father, AbstractDelegatableObject son)
  {
    if (father == null) {
      return new HashSet<AbstractClassModel>();
    }
    for (OperationModel operationModel : father.getOperationModel()) {
      son.addOperationModel(operationModel);
    }
    return father.getImplementsModel();
  }



  /**
   * getter for the collectionManager property.
   * 
   * @return the collectionManager
   */
  public CollectionManager getCollectionManager()
  {
    return collectionManager;
  }



  /**
   * setter for the collectionManager property.
   * 
   * @param collectionManager
   *          the collectionManager to set
   */
  public void setCollectionManager(CollectionManager collectionManager)
  {
    this.collectionManager = collectionManager;
  }



  /**
   * getter for the associationManager property.
   * 
   * @return the associationManager
   */
  public AssociationManager getAssociationManager()
  {
    return associationManager;
  }



  /**
   * setter for the associationManager property.
   * 
   * @param associationManager
   *          the associationManager to set
   */
  public void setAssociationManager(AssociationManager associationManager)
  {
    this.associationManager = associationManager;
  }

}
